const std = @import("std");
const awtk = @import("awtk");
const object = @import("object.zig");
const model_list = @import("model_list.zig");

const c = awtk.c;

var inited = false;

pub const ViewModel = c.view_model_t;
pub const NavigatorRequest = c.navigator_request_t;

pub fn navigatorRetToError(ret: c.ret_t) !void {
    return switch (ret) {
        c.RET_OK => return,
        c.RET_OOM => error.NavigatorOOM,
        c.RET_NOT_FOUND => error.NavigatorUINotFound,
        c.RET_FAIL => error.NavigatorFail,
        else => error.NavigatorUnknownError,
    };
}

pub inline fn navigatorTo(window: [:0]const u8) !void {
    const ret = c.navigator_to(window);
    return navigatorRetToError(ret);
}

pub const Error = error{
    NoArg,
    ArgTypeError,
};

pub fn init() !void {
    if (inited) {
        return error.DualInitialization;
    }

    const ret = c.mvvm_init();
    if (ret != c.RET_OK) {
        return error.MvvmInitFailed;
    }

    inited = true;
}

pub fn deinit() void {
    _ = c.mvvm_deinit();
    inited = false;
}

fn setEventForward(Type: type, ptr: anytype, target: *anyopaque) void {
    if (comptime std.meta.hasMethod(Type, "emitter")) {
        const emitter = ptr.emitter();
        _ = c.emitter_on(emitter, c.EVT_PROP_CHANGED, c.emitter_forward, target);
        _ = c.emitter_on(emitter, c.EVT_PROPS_CHANGED, c.emitter_forward, target);
        _ = c.emitter_on(emitter, c.EVT_ITEMS_CHANGED, c.emitter_forward, target);
        //std.log.debug("set event forward for {}", .{Type});
    }
}

fn setEventForwardAll(Type: type, ptr: anytype, target: *anyopaque) void {
    setEventForward(Type, ptr, target);

    const TypeInfo = @typeInfo(Type);
    inline for (TypeInfo.@"struct".fields) |field| {
        setEventForward(field.type, &@field(ptr, field.name), target);
    }
}

pub fn Object(Model: type) type {
    return struct {
        const VMObject = object.GenericObject(ViewModel, Model, .{ .on_destroy = onDestroy });

        pub fn create(req: ?*NavigatorRequest) callconv(.c) ?*ViewModel {
            std.debug.assert(inited);

            const obj: *awtk.Object = c.tk_object_create(VMObject.VTable()) orelse return null;
            const vm = c.view_model_init(@ptrCast(obj));

            const vm_object: *VMObject = @ptrCast(obj);
            const err: anyerror!void = blk: {
                if (comptime std.meta.hasFn(Model, "initWithReq")) {
                    break :blk vm_object.model.initWithReq(req);
                } else if (comptime std.meta.hasFn(Model, "init")) {
                    break :blk vm_object.model.init();
                }
            };

            // TODO 处理model init异常情况
            err catch |e| {
                std.log.err("{s} init: {}", .{ VMObject.VTable().type, e });
            };

            setEventForwardAll(Model, &vm_object.model, vm);

            return vm;
        }

        // overwrite VMObject.onDestroy
        fn onDestroy(obj: ?*awtk.Object) callconv(.c) c.ret_t {
            const ret: c.ret_t = VMObject.onDestroy(obj);
            var super = VMObject.cast(obj) orelse return c.RET_BAD_PARAMS;

            _ = c.view_model_deinit(&super.object);
            return ret;
        }
    };
}

// 向MVVM注册view model
pub fn register(VM: type) void {
    std.debug.assert(inited);

    const vm_name = awtk.baseTypeName(VM);
    const ObjVM = Object(VM);
    const ret = c.view_model_factory_register(vm_name, ObjVM.create);
    if (ret == c.RET_OK) {
        std.log.debug("registered view-model: {s}", .{vm_name});
    } else {
        std.log.warn("view-model {s} register failed!", .{vm_name});
    }
}

pub const StringArgs = struct {
    map: Map,

    const Map = std.StringHashMap([]const u8);

    pub fn init(cargs: ?[]const u8) StringArgs {
        var strings = StringArgs{ .map = Map.init(awtk.gallocator) };
        const args = cargs orelse return strings;
        const head = "string?";
        if (!std.mem.startsWith(u8, args, head)) {
            return strings;
        }

        const arg_strs = args[head.len..];
        var it = std.mem.tokenizeScalar(u8, arg_strs, '&');
        while (it.next()) |arg| {
            const p = std.mem.indexOfScalar(u8, arg, '=') orelse continue;
            const name = arg[0..p];
            const value = arg[p + 1 ..];
            strings.map.put(name, value) catch {
                std.log.err("StringArgs OOM on init!", .{});
            };
        }

        return strings;
    }

    pub fn deinit(self: *StringArgs) void {
        self.map.deinit();
    }

    pub fn count(self: StringArgs) u32 {
        return self.map.count();
    }

    pub fn getStr(self: StringArgs, key: []const u8) ![]const u8 {
        return self.map.get(key) orelse return Error.NoArg;
    }

    pub fn get(self: StringArgs, key: []const u8, ValueType: type) !ValueType {
        const str = try self.getStr(key);
        if (@TypeOf(str) == ValueType) {
            return str;
        }

        return switch (@typeInfo(ValueType)) {
            .bool => if (std.mem.eql(u8, str, "true")) true else false,
            .int => try std.fmt.parseInt(ValueType, str, 10),
            .float => try std.fmt.parseFloat(ValueType, str),
            else => Error.ArgTypeError,
        };
    }
};

test StringArgs {
    awtk.gallocator = std.testing.allocator;

    var a = StringArgs.init("");
    defer a.deinit();
    try std.testing.expectEqual(a.count(), 0);

    var b = StringArgs.init("string?");
    defer b.deinit();
    try std.testing.expectEqual(b.count(), 0);

    var d = StringArgs.init("string?index=2");
    defer d.deinit();
    try std.testing.expectEqual(d.count(), 1);
    try std.testing.expectEqual(d.get("index", u32), 2);
    try std.testing.expectEqual(d.get("index", i5), 2);

    var e = StringArgs.init("string?index=2&name=abc");
    defer e.deinit();
    try std.testing.expectEqual(e.count(), 2);
    try std.testing.expectEqual(e.get("index", u32), 2);
    try std.testing.expect(std.mem.eql(u8, try e.getStr("name"), "abc"));
    try std.testing.expectEqual(e.getStr("name"), e.get("name", []const u8));

    var g = StringArgs.init("string?index=2&name=abc&index=3");
    defer g.deinit();
    try std.testing.expectEqual(g.count(), 2);
    try std.testing.expectEqual(g.get("index", u32), 3);
}
